import { Writable } from 'stream';

/**
 * Both inclusive
 */
export interface Range {
	start: number;
	end: number;
}

interface RangeCallback {
	(chunk: Buffer, range: Range): Promise<void>;
}

class PartStream extends Writable {
	private part: number;
	private buffer?: Buffer;
	private offset: number;
	private bufferSize = 0;
	constructor(private ranges: Range[], private rangeCallback: RangeCallback) {
		super();
		this.part = 0;
		this.offset = 0;
	}
	override async _write(chunk: Buffer, encoding: BufferEncoding, done: (error?: Error | null) => void): Promise<void> {
		while (chunk.length > 0 && this.part < this.ranges.length) {
			const range = this.ranges[this.part];
			if (!this.buffer) {
				this.buffer = Buffer.alloc(range.end - range.start + 1);
			}
			const start = Math.max(this.offset, range.start);
			const end = Math.min(this.offset + chunk.length, range.end + 1);
			if (end > start) {
				chunk.copy(this.buffer, this.bufferSize, start - this.offset, end - this.offset);
				this.bufferSize += end - start;
				if (this.bufferSize === this.buffer.length) {
					await this.rangeCallback(this.buffer, range);
					this.bufferSize = 0;
					delete this.buffer;
					this.part++;
				}
			}
			if (this.offset + chunk.length > range.end + 1) {
				const remainingSize = this.offset + chunk.length - (range.end + 1);
				this.offset += chunk.length - remainingSize;
				chunk = chunk.slice(chunk.length - remainingSize);
			} else {
				this.offset += chunk.length;
				chunk = Buffer.alloc(0);
			}
		}
		done();
	}
}

export function parts(ranges: Range[], rangeCallback: RangeCallback) {
	return new PartStream(ranges, rangeCallback);
}
